using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace MassSpring
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        public ParticleSystem particleSystem;

        BasicEffect basicEffect;
        Effect jelloEffect;
        Effect jelloDepthMapEffect;
        Texture2D jelloTexture;
        Texture2D jelloNormalTexture;

        Effect DepthMapEffect;
        Effect BlurEffect;
        Effect DOFEffect;
        RenderTarget2D DepthTarget;
        RenderTarget2D SceneTarget;
        RenderTarget2D preBlurTarget;
        RenderTarget2D BlurTarget;
        RenderTarget2D shadowMapTarget0;
        RenderTarget2D shadowMapTarget1;
        RenderTarget2D shadowMapTarget2;
        RenderTarget2D finalTarget; //Target for the final scene

        Vector3 dirLight1Position = new Vector3(10,20,-70);
        Vector3 dirLight1Target = new Vector3(0,0,0);
        Vector3 dirLight1Up = Vector3.Left;
        Matrix dirLight1Projection;
        Vector3 dirLight2Position = new Vector3(-20, 50, 50);
        Vector3 dirLight2Target = new Vector3(0, 0, 0);
        Vector3 dirLight2Up = Vector3.Left;
        Matrix dirLight2Projection;
        Vector3 dirLight3Position = new Vector3(20, 50, -20);
        Vector3 dirLight3Target = new Vector3(0, 0, 0);
        Vector3 dirLight3Up = Vector3.Left;
        Matrix dirLight3Projection;
        public static Game Instance = null;
        List<JelloCube> cubeList = new List<JelloCube>();

        public FreeFlightCamera camera;

        #region Nodes/indices/verts
        PhysicsNode[] ropeNodes;
        int[] ropeIndices;
        VertexPositionColor[] ropeVertices;

        PhysicsNode[,] clothNodes;
        int[] clothIndices;
        VertexPositionColor[] clothVertices;

        int[] planeIndices;
        VertexPositionNormalColor[] planeVertices;

        public struct VertexPositionNormalColor : IVertexType
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Color Color;

            public VertexPositionNormalColor(Vector3 Position, Vector3 Normal, Color Color)
            {
                this.Position = Position;
                this.Normal = Normal;
                this.Color = Color;
            }
            
            public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
            (
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                new VertexElement(Marshal.SizeOf(typeof(Vector3)), VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
                new VertexElement(Marshal.SizeOf(typeof(Vector3)) * 2, VertexElementFormat.Color, VertexElementUsage.Color, 0)
            );

            VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } }
        }

        public struct JelloVertexFormat : IVertexType
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Color Color;
            public Vector2 Texcoord;
            public Vector3 Tangent;
            public Vector3 Binormal;

            public JelloVertexFormat(Vector3 Position, Vector3 Normal, Color Color, Vector2 Texcoord, Vector3 Tangent, Vector3 Binormal)
            {
                this.Position = Position;
                this.Normal = Normal;
                this.Color = Color;
                this.Texcoord = Texcoord;
                this.Tangent = Tangent;
                this.Binormal = Binormal;
            }

            public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
            (
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                new VertexElement(Marshal.SizeOf(typeof(Vector3)), VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
                new VertexElement(Marshal.SizeOf(typeof(Vector3)) * 2, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                new VertexElement(Marshal.SizeOf(typeof(Vector3)) * 2 + Marshal.SizeOf(typeof(Color)), VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
                new VertexElement(Marshal.SizeOf(typeof(Vector3)) * 2 + Marshal.SizeOf(typeof(Color)) + Marshal.SizeOf(typeof(Vector2)), VertexElementFormat.Vector3, VertexElementUsage.Tangent,0),
                new VertexElement(Marshal.SizeOf(typeof(Vector3)) * 3 + Marshal.SizeOf(typeof(Color)) + Marshal.SizeOf(typeof(Vector2)), VertexElementFormat.Vector3, VertexElementUsage.Binormal, 0)
            );

            VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } }
        }
        #endregion

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 1680;
            graphics.PreferredBackBufferHeight = 1050;
            dirLight1Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)graphics.PreferredBackBufferWidth / (float)graphics.PreferredBackBufferHeight, 1f, 300.0f);
            dirLight2Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)graphics.PreferredBackBufferWidth / (float)graphics.PreferredBackBufferHeight, 1f, 300.0f);
            dirLight3Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)graphics.PreferredBackBufferWidth / (float)graphics.PreferredBackBufferHeight, 1f, 300.0f);
            Content.RootDirectory = "Content";
            Instance = this;
            camera = new FreeFlightCamera(Vector3.UnitZ);
            this.Components.Add(new FrameRateCounter(this));
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            setupSimulation();            
            base.Initialize();
        }

        private void setupSimulation()
        {

            particleSystem = new ParticleSystem(new Vector3(0, -9.8f, 0), Vector3.Zero, -20.0f);

            initRope();
            initRopeVerts();

            initCloth();
            initClothVerts();

            cubeList.Add(new JelloCube(Color.Red, this, new Vector3(0,20,0), Matrix.Identity,8, 8, 3, 2.0f,2.0f,2.0f));
            cubeList[0].init();
            cubeList.Add(new JelloCube(Color.White, this, new Vector3(10, 10, -10), Matrix.Identity, 4, 4, 4, 2.0f, 2.0f, 2.0f));
            cubeList[1].init();
            cubeList.Add(new JelloCube(Color.LightGreen, this, new Vector3(-10, 3, -10), Matrix.CreateFromAxisAngle(Vector3.Up, MathHelper.PiOver4), 5, 5, 5, 2.0f, 2.0f, 2.0f));
            cubeList[2].init();
            cubeList.Add(new JelloCube(Color.Azure, this, new Vector3(-10, 40, 10), Matrix.Identity, 6, 3, 6, 1.0f, 1.0f, 4.0f));
            cubeList[3].init();
            cubeList.Add(new JelloCube(Color.Goldenrod, this, new Vector3(10, 2, 10), Matrix.Identity, 3, 7, 7, 2.0f, 2.0f, 2.0f));
            cubeList[4].init();
            initPlaneVerts();
        }

        #region rope/cloth/cube inits
        private void initRope()
        {
            const float ROPESTRUCTURALSPRINGCONSTANT = 50;
            const float ROPEFLEXIONSPRINGCONSTANT = 8;
            const float ROPEEDGEDAMPING = 0.01f;
            const float ROPELENGTH = 5.0f;
            const float ROPEPARTICLEMASS = 0.001f;
            const int ROPENUMNODES = 200;

            ropeNodes = new PhysicsNode[ROPENUMNODES];
	        for (int i = 0 ; i < ROPENUMNODES; i++)
	        {
		        PhysicsNode pnode = new PhysicsNode();
                pnode.Position = new Vector3(0,10,i*ROPELENGTH/(float)ROPENUMNODES);
                pnode.Mass = ROPEPARTICLEMASS;
                pnode.Damping = 0.80f;
		        if (i==0)
			        pnode.Immovable = true;
		        particleSystem.AddNode(pnode);
		        ropeNodes[i] = pnode;
	        }

	        //Add the structural springs
	        for (int i = 1; i < ROPENUMNODES; i++)
	        {
		        Spring spr = new Spring(ROPESTRUCTURALSPRINGCONSTANT,ROPEEDGEDAMPING, ROPELENGTH/(float)ROPENUMNODES);
		        spr.Attach(ropeNodes[i-1], ropeNodes[i]);
		        particleSystem.AddSpring(spr);
	        }

	        //Add flexion springs
	        for (int i = 2; i < ROPENUMNODES; i++)
	        {
		        Spring spr = new Spring(ROPEFLEXIONSPRINGCONSTANT,ROPEEDGEDAMPING, 2*ROPELENGTH/(float)ROPENUMNODES);
		        spr.Attach(ropeNodes[i-2], ropeNodes[i]);
		        particleSystem.AddSpring(spr);
	        }
        }

        private void initCloth()
        {
            const float CLOTHSTRUCTURALSPRINGCONSTANT = 50;
            const float CLOTHSHEARSPRINGCONSTANT = 50;
            const float CLOTHFLEXIONSPRINGCONSTANT = 15;
            const float CLOTHEDGEDAMPING = 0.01f;
            const float CLOTHPARTICLEMASS = 0.1f;
            const float CLOTHPARTICLEDISTANCE = 1.0f; //Distance between particles
            const int CLOTHNUMNODESX = 10; //Length of the cloth in the X direction
            const int CLOTHNUMNODESZ = 10; //Length of the cloth in the Z direction
            clothNodes = new PhysicsNode[CLOTHNUMNODESX, CLOTHNUMNODESZ];

	        for (int i = 0 ; i < CLOTHNUMNODESX; i++)
		        for (int j= 0 ; j < CLOTHNUMNODESZ; j++)
		        {
			        PhysicsNode p = new PhysicsNode();
			        p.Position = new Vector3(30 + i*CLOTHPARTICLEDISTANCE,10,j*CLOTHPARTICLEDISTANCE);
			        p.Mass = CLOTHPARTICLEMASS;
			        p.Damping = 0.8f;
			        if (j == 0 && (i ==0 || i==(CLOTHNUMNODESX-1)))
				        p.Immovable = true;
			        particleSystem.AddNode(p);
			        clothNodes[i,j] = p;
		        }

	        //Add structural springs in the X direction
	        for (int i = 1 ; i < CLOTHNUMNODESX; i++)
		        for (int j= 0 ; j < CLOTHNUMNODESZ; j++)
		        {
			        Spring spr = new Spring(CLOTHSTRUCTURALSPRINGCONSTANT, CLOTHEDGEDAMPING, CLOTHPARTICLEDISTANCE);
			        spr.Attach(clothNodes[i,j], clothNodes[i-1,j]);
			        particleSystem.AddSpring(spr);
		        }

	        //Add structural springs in the Z direction
	        for (int i = 0 ; i < CLOTHNUMNODESX; i++)
		        for (int j= 1 ; j < CLOTHNUMNODESZ; j++)
		        {
			        Spring spr = new Spring(CLOTHSTRUCTURALSPRINGCONSTANT,CLOTHEDGEDAMPING,  CLOTHPARTICLEDISTANCE);
			        spr.Attach(clothNodes[i,j], clothNodes[i,j-1]);
			        particleSystem.AddSpring(spr);
		        }

	        //Add crossstructural springs
	        for (int i = 1 ; i < CLOTHNUMNODESX; i++)
		        for (int j= 1 ; j < CLOTHNUMNODESZ; j++)
		        {
			        Spring spr = new Spring(CLOTHSHEARSPRINGCONSTANT, CLOTHEDGEDAMPING, (float)Math.Sqrt(2*CLOTHPARTICLEDISTANCE*CLOTHPARTICLEDISTANCE));
			        spr.Attach(clothNodes[i,j], clothNodes[i-1,j-1]);
			        particleSystem.AddSpring(spr);
		        }

	        for (int i = 0 ; i < CLOTHNUMNODESX-1; i++)
		        for (int j= 1 ; j < CLOTHNUMNODESZ; j++)
		        {
			        Spring spr = new Spring(CLOTHSHEARSPRINGCONSTANT, CLOTHEDGEDAMPING, (float)Math.Sqrt(2*CLOTHPARTICLEDISTANCE*CLOTHPARTICLEDISTANCE));
			        spr.Attach(clothNodes[i,j], clothNodes[i+1,j-1]);
			        particleSystem.AddSpring(spr);
		        }

	        //Add flexion springs in the X direction
	        for (int i = 2 ; i < CLOTHNUMNODESX; i++)
		        for (int j= 0 ; j < CLOTHNUMNODESZ; j++)
		        {
			        Spring spr = new Spring(CLOTHFLEXIONSPRINGCONSTANT, CLOTHEDGEDAMPING, CLOTHPARTICLEDISTANCE*2);
			        spr.Attach(clothNodes[i,j], clothNodes[i-2,j]);
			        particleSystem.AddSpring(spr);
		        }

	        //Add flexion springs in the Z direction
	        for (int i = 0; i < CLOTHNUMNODESX; i++)
		        for (int j= 2 ; j < CLOTHNUMNODESZ; j++)
		        {
			        Spring spr = new Spring(CLOTHFLEXIONSPRINGCONSTANT,CLOTHEDGEDAMPING,  CLOTHPARTICLEDISTANCE*2);
			        spr.Attach(clothNodes[i,j], clothNodes[i,j-2]);
			        particleSystem.AddSpring(spr);
		        }

	        //Add cloth wind triangles to the particle system
	        for (int i = 0; i < CLOTHNUMNODESX-1; i++)
		        for (int j= 0 ; j < CLOTHNUMNODESZ-1; j++)
                    particleSystem.AddWindTriangle(clothNodes[i, j], clothNodes[i + 1, j], clothNodes[i, j + 1]);
	        for (int i = 1; i < CLOTHNUMNODESX; i++)
		        for (int j= 1; j < CLOTHNUMNODESZ; j++)
                    particleSystem.AddWindTriangle(clothNodes[i, j], clothNodes[i - 1, j], clothNodes[i, j - 1]);
        }

        private void initRopeVerts()
        {
            ropeVertices = new VertexPositionColor[ropeNodes.Length];
            for (int i = 0; i < ropeNodes.Length; i++)
                ropeVertices[i] = new VertexPositionColor(ropeNodes[i].Position, Color.Yellow);

            ropeIndices = new int[(ropeNodes.Length - 1) * 2];
            for (int i = 0; i < ropeNodes.Length - 1; i++)
            {
                ropeIndices[i*2] = i;
                ropeIndices[i*2+1] = i + 1;
            }
        }

        private void initClothVerts()
        {
            clothVertices = new VertexPositionColor[clothNodes.Length];
            for (int i = 0; i < clothNodes.GetLength(0); i++)
                for (int j = 0; j < clothNodes.GetLength(1); j++)
                    clothVertices[i + j * clothNodes.GetLength(0)] = new VertexPositionColor(clothNodes[i, j].Position, interpolateColors(Color.Red, Color.Blue, (float)j / (float)clothNodes.GetLength(1)));

            clothIndices = new int[(clothNodes.GetLength(0) - 1) * (clothNodes.GetLength(1) - 1) * 6];
            int currInd = 0;
            for (int i = 0; i < clothNodes.GetLength(0)- 1; i++)
                for (int j = 0; j < clothNodes.GetLength(1) - 1; j++)
                {
                    clothIndices[currInd] = i + j * clothNodes.GetLength(0); currInd++;
                    clothIndices[currInd] = (i+1) + j * clothNodes.GetLength(0); currInd++;
                    clothIndices[currInd] = i + (j+1) * clothNodes.GetLength(0); currInd++;
                    clothIndices[currInd] = i + (j + 1) * clothNodes.GetLength(0); currInd++;
                    clothIndices[currInd] = (i + 1) + j * clothNodes.GetLength(0); currInd++;
                    clothIndices[currInd] = (i+1)+ (j+1) * clothNodes.GetLength(0); currInd++;
                }
        }

        private void initPlaneVerts()
        {
            planeVertices = new VertexPositionNormalColor[4];

            planeVertices[0] = new VertexPositionNormalColor(new Vector3(60, -20, 60), Vector3.Up, Color.White);
            planeVertices[1] = new VertexPositionNormalColor(new Vector3(60, -20, -60), Vector3.Up, Color.White);
            planeVertices[2] = new VertexPositionNormalColor(new Vector3(-60, -20, 60), Vector3.Up, Color.White);
            planeVertices[3] = new VertexPositionNormalColor(new Vector3(-60, -20, -60), Vector3.Up, Color.White);

            planeIndices = new int[6];

            planeIndices[0] = 0;
            planeIndices[1] = 2;
            planeIndices[2] = 1;
            planeIndices[3] = 1;
            planeIndices[4] = 2;
            planeIndices[5] = 3;
        }

        private Color interpolateColors(Color c1, Color c2, float a)
        {
            return new Color(((1 - a) * c1.R + (a * c2.R)) / 255.0f,
                ((1 - a) * c1.G + (a * c2.G)) / 255.0f,
                ((1 - a) * c1.B + (a * c2.B)) / 255.0f,
                ((1-a) * c1.A + (a * c2.A))/255.0f);
        }
#endregion

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            basicEffect = new BasicEffect(GraphicsDevice);
            jelloEffect = Content.Load<Effect>("JelloEffect");
            jelloDepthMapEffect = Content.Load<Effect>("JelloDepth");
            jelloTexture = Content.Load<Texture2D>("jello");
            jelloNormalTexture = Content.Load<Texture2D>("jellonormal");
            foreach (JelloCube cube in cubeList)
                cube.loadContent();

            DepthMapEffect = Content.Load<Effect>("DepthShader");
            BlurEffect = Content.Load<Effect>("BlurEffect");
            DOFEffect = Content.Load<Effect>("DOFShader");
            DepthTarget = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, true, SurfaceFormat.Single, DepthFormat.Depth24);
            shadowMapTarget0 = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, true, SurfaceFormat.Single, DepthFormat.Depth24);
            shadowMapTarget1 = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, true, SurfaceFormat.Single, DepthFormat.Depth24);
            shadowMapTarget2 = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, true, SurfaceFormat.Single, DepthFormat.Depth24);
            preBlurTarget = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, true, GraphicsDevice.DisplayMode.Format, DepthFormat.Depth24);
            BlurTarget = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, true, GraphicsDevice.DisplayMode.Format, DepthFormat.Depth24);
            SceneTarget = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, true, GraphicsDevice.DisplayMode.Format, DepthFormat.Depth24);
            finalTarget = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, true, GraphicsDevice.DisplayMode.Format, DepthFormat.Depth24); 
            camera.LoadContent();

            RasterizerState rs = new RasterizerState();
            rs.CullMode = CullMode.None;
            GraphicsDevice.RasterizerState = rs;
            GraphicsDevice.PresentationParameters.RenderTargetUsage = RenderTargetUsage.PreserveContents;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        float underTime = 0.0f;
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed
                || Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            if (GamePad.GetState(PlayerIndex.One).Buttons.A == ButtonState.Pressed
                || Keyboard.GetState().IsKeyDown(Keys.R))
                this.setupSimulation();

            const float PARTICLESYSTEMSTEP = 0.0035f;
            float elapsed = gameTime.ElapsedGameTime.Milliseconds / 1000.0f;
            camera.Update(gameTime);
            elapsed += underTime;
            while (elapsed > 0)
            {
                particleSystem.Integrate(PARTICLESYSTEMSTEP);
                elapsed -= PARTICLESYSTEMSTEP;
            }
            underTime = elapsed;
            #region update vertices
            //Update vertices
            for (int i = 0; i < ropeNodes.Length; i++)
                ropeVertices[i].Position = ropeNodes[i].Position;

            for (int i = 0; i < clothNodes.GetLength(0); i++)
                for (int j = 0; j < clothNodes.GetLength(1); j++)
                    clothVertices[i + j * clothNodes.GetLength(0)].Position = clothNodes[i, j].Position;

            foreach (JelloCube cube in cubeList)
                cube.Update();
            #endregion
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            Window.Title = "" + camera.CameraPosition;
            foreach (JelloCube cube in cubeList)
                DrawJelloDepth(gameTime, cube);
            DrawSceneDepth(gameTime, this.DepthTarget, Matrix.Identity, camera.CameraViewMatrix, Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1f, 100.0f));
            DrawSceneDepth(gameTime, this.shadowMapTarget0, Matrix.Identity, Matrix.CreateLookAt(dirLight1Position, dirLight1Target, dirLight1Up), dirLight1Projection);
            DrawSceneDepth(gameTime, this.shadowMapTarget1, Matrix.Identity, Matrix.CreateLookAt(dirLight2Position, dirLight2Target, dirLight2Up), dirLight2Projection);
            DrawSceneDepth(gameTime, this.shadowMapTarget2, Matrix.Identity, Matrix.CreateLookAt(dirLight3Position, dirLight3Target, dirLight3Up), dirLight3Projection);

            //Do the actual drawing.
            DrawSceneToTarget(gameTime, SceneTarget);
            BlurCurrentTarget(gameTime);
            //DrawWithFOD(gameTime, finalTarget);
            DrawWithFOD(gameTime, null);
            //DrawMultipleTargetsToScreen(gameTime, finalTarget, shadowMapTarget);
            base.Draw(gameTime);
        }

        private void DrawMultipleTargetsToScreen(GameTime gameTime, RenderTarget2D mainTarget, RenderTarget2D secondaryTarget)
        {
            graphics.GraphicsDevice.SetRenderTarget(null);
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin();
            spriteBatch.Draw(mainTarget, new Rectangle(0, 0, GraphicsDevice.Viewport.Width / 4 * 3, GraphicsDevice.Viewport.Height / 4 * 3), Color.White);
            spriteBatch.Draw(secondaryTarget, new Rectangle(GraphicsDevice.Viewport.Width / 4 * 3, GraphicsDevice.Viewport.Height / 4 * 3, GraphicsDevice.Viewport.Width / 4, GraphicsDevice.Viewport.Height / 4), Color.White);
            spriteBatch.End();
        }

        private void DrawWithFOD(GameTime gameTime, RenderTarget2D target)
        {
            graphics.GraphicsDevice.SetRenderTarget(target);
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            DOFEffect.CurrentTechnique = DOFEffect.Techniques["Technique1"];

            float focusDistance = 20.0f;
            float focusRange = 10.0f;
            float nearClip = 1.0f;
            float farClip = 100.0f;
            farClip = farClip / (farClip - nearClip);

            DOFEffect.Parameters["Distance"].SetValue(focusDistance);
            DOFEffect.Parameters["Range"].SetValue(focusRange);
            DOFEffect.Parameters["Near"].SetValue(nearClip);
            DOFEffect.Parameters["Far"].SetValue(farClip);
            DOFEffect.Parameters["D1M"].SetValue(DepthTarget);
            DOFEffect.Parameters["BlurScene"].SetValue(BlurTarget);

            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque);
            foreach (EffectPass pass in DOFEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                spriteBatch.Draw(SceneTarget, new Rectangle(0, 0, BlurTarget.Width, BlurTarget.Height), Color.White);
            }
            spriteBatch.End();
            graphics.GraphicsDevice.SetRenderTarget(null);
        }

        private void BlurCurrentTarget(GameTime gameTime)
        {
            BlurEffect.CurrentTechnique = BlurEffect.Techniques["Technique1"];
            BlurEffect.Parameters["screenHeight"].SetValue((float)GraphicsDevice.Viewport.Height);
            BlurEffect.Parameters["screenWidth"].SetValue((float)GraphicsDevice.Viewport.Width);
            BlurEffect.Parameters["blurPixels"].SetValue(1.0f);

            graphics.GraphicsDevice.SetRenderTarget(preBlurTarget);
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque);
            foreach (EffectPass pass in BlurEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                spriteBatch.Draw(SceneTarget, new Rectangle(0, 0, BlurTarget.Width, BlurTarget.Height), Color.White);
            }
            spriteBatch.End();

            graphics.GraphicsDevice.SetRenderTarget(BlurTarget);
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque);
            foreach (EffectPass pass in BlurEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                spriteBatch.Draw(preBlurTarget, new Rectangle(0, 0, BlurTarget.Width, BlurTarget.Height), Color.White);
            }
            spriteBatch.End();
        }

        private void DrawSceneToTarget(GameTime gameTime, RenderTarget2D target)
        {
            //Sort cubes before drawing
            cubeList.Sort();

            GraphicsDevice.SetRenderTarget(target);
            GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.CornflowerBlue, float.MaxValue, 0);
            DrawPlane(gameTime);
            foreach (JelloCube cube in cubeList)
            {
                DrawJello(gameTime, cube);
                GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.White, float.MaxValue, 0);
            }
        }

        private void DrawSceneDepth(GameTime gameTime, RenderTarget2D target, Matrix world, Matrix view, Matrix projection)
        {
            GraphicsDevice.SetRenderTarget(target);
            GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.Black, float.MaxValue, 0);

            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;

            DepthMapEffect.CurrentTechnique = DepthMapEffect.Techniques["DepthMapShader"];
            //@TODO World matrix var instead of Identity
            DepthMapEffect.Parameters["World"].SetValue(world);
            DepthMapEffect.Parameters["View"].SetValue(view);
            //@TODO Projection matrix var instead of copy create
            DepthMapEffect.Parameters["Projection"].SetValue(projection);
            foreach (EffectPass pass in DepthMapEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                //GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalColor>(PrimitiveType.TriangleList, planeVertices, 0, planeVertices.Length, planeIndices, 0, planeIndices.Length / 3);
                foreach(JelloCube cube in cubeList)
                    GraphicsDevice.DrawUserPrimitives<JelloVertexFormat>(PrimitiveType.TriangleList, cube.cubeVertices, 0, cube.cubeVertices.Length / 3, JelloVertexFormat.VertexDeclaration);
            }
        }

        private void DrawPlane(GameTime gameTime)
        {
            //Set renderstate before drawing 3d
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;

            //GraphicsDevice.Clear(Color.CornflowerBlue);
            jelloEffect.CurrentTechnique = jelloEffect.Techniques["flat"];
            SetJelloEffectParameters(null);

            foreach (EffectPass pass in jelloEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalColor>(PrimitiveType.TriangleList, planeVertices, 0, planeVertices.Length, planeIndices, 0, planeIndices.Length / 3);
            }
        }

        private void DrawJelloDepth(GameTime gameTime, JelloCube cube)
        {
            GraphicsDevice.SetRenderTarget(cube.jelloDepthTarget);
            DepthStencilState dss = new DepthStencilState();
            dss.DepthBufferEnable = true;
            dss.DepthBufferWriteEnable = true;
            dss.DepthBufferFunction = CompareFunction.GreaterEqual;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;

            GraphicsDevice.DepthStencilState = dss;
            GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,Color.Black, float.MinValue, 0);
            GraphicsDevice.RasterizerState= RasterizerState.CullNone;
            GraphicsDevice.BlendState = BlendState.Opaque;
            jelloDepthMapEffect.CurrentTechnique = jelloDepthMapEffect.Techniques["Technique1"];
            jelloDepthMapEffect.Parameters["World"].SetValue(Matrix.Identity);
            Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1f, 500.0f);
            jelloDepthMapEffect.Parameters["Projection"].SetValue(proj);
            jelloDepthMapEffect.Parameters["View"].SetValue(camera.CameraViewMatrix);
            jelloDepthMapEffect.Parameters["cameraPosition"].SetValue(camera.CameraPosition);
            foreach (EffectPass pass in jelloDepthMapEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                //GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, cubeVertices, 0, cubeVertices.Length, cubeIndices, 0, cubeIndices.Length / 3);
                GraphicsDevice.DrawUserPrimitives<JelloVertexFormat>(PrimitiveType.TriangleList, cube.cubeVertices, 0, cube.cubeVertices.Length / 3, JelloVertexFormat.VertexDeclaration);
            }
            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
        }

        private void SetJelloEffectParameters(JelloCube cube = null)
        {
            int numPointLights = 4;
            Vector3[] pointLightPositions = new Vector3[numPointLights];
            float[] pointLightIntensities = new float[numPointLights];
            Vector4[] pointLightColors = new Vector4[numPointLights];
            pointLightPositions[0] = new Vector3(10,10,20);
            pointLightIntensities[0] = 200.0f;
            pointLightColors[0] = Color.Red.ToVector4();
            pointLightPositions[1] = new Vector3(0, 10, -30);
            pointLightIntensities[1] = 200.0f;
            pointLightColors[1] = Color.Blue.ToVector4();
            pointLightPositions[2] = new Vector3(20, 10, 15);
            pointLightIntensities[2] = 200.0f;
            pointLightColors[2] = Color.Yellow.ToVector4();
            pointLightPositions[3] = new Vector3(-20, 10, -20);
            pointLightIntensities[3] = 300.0f;
            pointLightColors[3] = Color.White.ToVector4();

            int numDirectionalLights = 3;
            Vector3[] directionalLightDirections = new Vector3[numDirectionalLights];
            float[] directionalLightIntensities = new float[numDirectionalLights];
            Vector4[] directionalLightColors = new Vector4[numDirectionalLights];
            Matrix[] directionalLightWorlds = new Matrix[numDirectionalLights];
            Matrix[] directionalLightViews = new Matrix[numDirectionalLights];
            Matrix[] directionalLightProjections = new Matrix[numDirectionalLights];

            directionalLightDirections[0] = dirLight1Target - dirLight1Position;
            directionalLightDirections[0].Normalize();
            directionalLightIntensities[0] = 0.2f;
            directionalLightColors[0] = Color.White.ToVector4();
            directionalLightWorlds[0] = Matrix.Identity;
            directionalLightViews[0] = Matrix.CreateLookAt(dirLight1Position, dirLight1Target, dirLight1Up);
            directionalLightProjections[0] = dirLight1Projection;

            directionalLightDirections[1] = dirLight2Target - dirLight2Position;
            directionalLightDirections[1].Normalize();
            directionalLightIntensities[1] = 0.2f;
            directionalLightColors[1] = Color.White.ToVector4();
            directionalLightWorlds[1] = Matrix.Identity;
            directionalLightViews[1] = Matrix.CreateLookAt(dirLight2Position, dirLight2Target, dirLight2Up);
            directionalLightProjections[1] = dirLight2Projection;

            directionalLightDirections[2] = dirLight3Target - dirLight3Position;
            directionalLightDirections[2].Normalize();
            directionalLightIntensities[2] = 0.2f;
            directionalLightColors[2] = Color.White.ToVector4();
            directionalLightWorlds[2] = Matrix.Identity;
            directionalLightViews[2] = Matrix.CreateLookAt(dirLight3Position, dirLight3Target, dirLight3Up);
            directionalLightProjections[2] = dirLight3Projection;

            jelloEffect.Parameters["shadowDepthMapTexture0"].SetValue(shadowMapTarget0);
            jelloEffect.Parameters["shadowDepthMapTexture1"].SetValue(shadowMapTarget1);
            jelloEffect.Parameters["shadowDepthMapTexture2"].SetValue(shadowMapTarget2);
            jelloEffect.Parameters["xDirectionalLightWorlds"].SetValue(directionalLightWorlds);
            jelloEffect.Parameters["xDirectionalLightViews"].SetValue(directionalLightViews);
            jelloEffect.Parameters["xDirectionalLightProjections"].SetValue(directionalLightProjections);
            jelloEffect.Parameters["World"].SetValue(Matrix.Identity);
            Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1f, 500.0f);
            jelloEffect.Parameters["Projection"].SetValue(proj);
            jelloEffect.Parameters["View"].SetValue(camera.CameraViewMatrix);
            jelloEffect.Parameters["cameraPosition"].SetValue(camera.CameraPosition);
            jelloEffect.Parameters["jelloAlpha"].SetValue(0.1f);
            jelloEffect.Parameters["maxJelloAlpha"].SetValue(0.8f);
            jelloEffect.Parameters["minJelloAlpha"].SetValue(0.6f);
            jelloEffect.Parameters["fInverseViewportDimensions"].SetValue(new Vector2(1f / (float)GraphicsDevice.Viewport.Width, 1f / (float)GraphicsDevice.Viewport.Height));
            jelloEffect.Parameters["numPointLights"].SetValue(numPointLights);
            jelloEffect.Parameters["xPointLightPositions"].SetValue(pointLightPositions);
            jelloEffect.Parameters["xPointLightIntensities"].SetValue(pointLightIntensities);
            jelloEffect.Parameters["xPointLightColors"].SetValue(pointLightColors);
            jelloEffect.Parameters["numDirectionalLights"].SetValue(numDirectionalLights);
            jelloEffect.Parameters["xDirectionalLightDirections"].SetValue(directionalLightDirections);
            jelloEffect.Parameters["xDirectionalLightIntensities"].SetValue(directionalLightIntensities);
            jelloEffect.Parameters["xDirectionalLightColors"].SetValue(directionalLightColors);
            jelloEffect.Parameters["xAmbientColor"].SetValue(Color.White.ToVector4());
            jelloEffect.Parameters["xAmbientIntensity"].SetValue(0.2f);
            jelloEffect.Parameters["jelloBumpTexture"].SetValue(jelloNormalTexture);
            if (cube != null)
                jelloEffect.Parameters["jelloDepthMap"].SetValue(cube.jelloDepthTarget);
        }

        private void DrawJello(GameTime gameTime, JelloCube cube)
        {
            //Draw jello cubes with seperate shader

            BlendState bs = new BlendState();
            //bs.SeparateAlphaBlendEnabled = true;
           
            bs.AlphaDestinationBlend = Blend.One;
            bs.AlphaSourceBlend = Blend.SourceAlpha;
            bs.ColorSourceBlend = Blend.SourceColor;
            bs.ColorDestinationBlend = Blend.InverseSourceAlpha;
            GraphicsDevice.BlendState = bs;
            SetJelloEffectParameters(cube);
            jelloEffect.CurrentTechnique = jelloEffect.Techniques["jello"];
            foreach (EffectPass pass in jelloEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawUserPrimitives<JelloVertexFormat>(PrimitiveType.TriangleList, cube.cubeVertices, 0, cube.cubeVertices.Length / 3, JelloVertexFormat.VertexDeclaration);
            }

            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.BlendState = BlendState.Opaque;
        }
    }
}
